package com.jo.factory;

import com.jo.anotation.*;
import com.jo.bean.BeanDefinition;
import com.jo.bean.Property;
import com.jo.util.IocUtil;
import com.jo.util.PackageUtil;
import com.jo.util.ReflectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJMethodBeforeAdvice;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * 默认的Bean工厂实现，实现了注册、解析bean，和获取bean
 * 获取配置给子类获取
 * @author Jo
 * @date 2017/8/24
 */
public class DefaultBeanFactory implements BeanFactory {

    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultBeanFactory.class);

    protected ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
    protected Set<String> beanNameSet = Collections.synchronizedSet(new HashSet<>());

    protected Map<String, Object> singletonMap = new ConcurrentHashMap<>();

    protected String configLocation;

    /**
     * 刷新容器，加载BeanDefinition，实例化singleton
     */
    protected void refresh() {
        //加载bean
        loadBeanDefinition(this.configLocation);

        //实例化singleton
        instantiationSingleton();
    }

    private int loadBeanDefinition(String location) {
        Document document = loadDocument(location);
        return parseDocument(document);
    }

    private Document loadDocument(String configLocation) {
        InputStream inputStream = getClass().getClassLoader().getResourceAsStream(configLocation);
        Document doc = null;
        SAXReader reader = new SAXReader();
        try {
            doc = reader.read(inputStream);
        } catch (DocumentException e) {
            LOGGER.error("获取Document失败，configLocation = {}", configLocation, e);
        }
        return doc;
    }

    private int parseDocument(Document document) {
        Assert.notNull(document, "document 不能为空");
        int totalCount = 0;
        Element root = document.getRootElement();
        totalCount += parseBeanFromXml(root);
        totalCount += parseBasePackage(root);
        return totalCount;
    }

    private int parseBasePackage(Element root) {
        int totalCount = 0;
        Element componentScan = root.element("component-scan");
        if (componentScan != null) {
            String basePackage = componentScan.attributeValue("base-package");
            if (StringUtils.isNotBlank(basePackage)) {
                totalCount += packageScan(basePackage);
            }
        }
        return totalCount;
    }

    private int parseBeanFromXml(Element root) {
        int totalCount = 0;
        List<Element> beans = root.elements("bean");
        for (Element beanElement : beans) {
            String beanName = beanElement.attributeValue("name");
            String id = beanElement.attributeValue("id");
            String className = beanElement.attributeValue("class");
            Class clazz = null;
            try {
                clazz = Class.forName(className);
            } catch (ClassNotFoundException e) {
                LOGGER.error("加载类失败, {}", className, e);
                throw new RuntimeException("加载类失败 " + className);
            }
            BeanDefinition beanDefinition = new BeanDefinition(id, beanName, clazz);
            List<Element> propertyElements = beanElement.elements("property");
            if (propertyElements != null) {
                for (Element propertyElement : propertyElements) {
                    String name = propertyElement.attributeValue("name");
                    String value = propertyElement.attributeValue("value");
                    String ref = propertyElement.attributeValue("ref");
                    Property property = new Property(name, value, ref, Object.class);
                    beanDefinition.getProperties().add(property);
                }
            }
            totalCount++;
            this.beanDefinitionMap.put(beanName, beanDefinition);
        }
        return totalCount;
    }

    private int packageScan(String basePackage) {
        int totalCount = 0;
        List<String> classNames = PackageUtil.getClassName(basePackage);
        if (!CollectionUtils.isEmpty(classNames)) {
            for (String className : classNames) {
                try {
                    Class clazz = Class.forName(className);
                    if (isMyComponent(clazz)) {
                        registerBeanDefinition(clazz);
                        totalCount++;
                    }
                } catch (ClassNotFoundException e) {
                    LOGGER.error("get class fail name : " + className, e);
                }
            }
            LOGGER.info("base package load {} class : {}", totalCount, basePackage);
            return totalCount;
        }
        LOGGER.info("base package not find class : {}", basePackage);
        return totalCount;
    }

    protected void registerBeanDefinition(Class clazz) {
        BeanDefinition beanDefinition = createBeanDefinition(clazz);
        registerBeanDefinition(beanDefinition);
    }

    protected void registerBeanDefinition(BeanDefinition beanDefinition) {
        beanDefinitionMap.put(beanDefinition.getBeanName(), beanDefinition);
        beanNameSet.add(beanDefinition.getBeanName());
    }

    protected BeanDefinition createBeanDefinition(Class clazz) {
        BeanDefinition beanDefinition = new BeanDefinition();
        String beanName = IocUtil.getMyComponentName(clazz);
        beanDefinition.setBeanId(beanName);
        beanDefinition.setBeanName(beanName);
        beanDefinition.setClazz(clazz);
        ReflectionUtils.doWithField(clazz, (field) -> {
            if (isMyAutoWire(field)) {
                String fieldName = field.getName();
                Property property = new Property(fieldName, null, fieldName, Object.class);
                beanDefinition.getProperties().add(property);
            }
        });
        return beanDefinition;
    }

    @Override
    public Object getBean(String name) {
        Object bean = singletonMap.get(name);
        if (bean == null) {
            BeanDefinition beanDefinition = beanDefinitionMap.get(name);
            if (beanDefinition != null) {
                bean = createBean(beanDefinition);
            }
        }
        return bean;
    }

    protected BeanDefinition getBeanDefinition(String name){
        return this.beanDefinitionMap.get(name);
    }

    protected Object createBean(BeanDefinition beanDefinition) {
        if (beanDefinition == null) {
            throw new RuntimeException("beanDefinition 不能为空");
        }
        Object bean = doCreateBean(beanDefinition);
        if (bean != null) {
            this.singletonMap.put(beanDefinition.getBeanName(), bean);
            populateBean(bean, beanDefinition);
        }
        return bean;
    }

    private Object doCreateBean(BeanDefinition beanDefinition) {
        Object bean;
        Class clazz = beanDefinition.getClazz();
        String className = clazz.getSimpleName();
        List<Property> constructorArgs = beanDefinition.getConstructorArgs();
        try {
            if (CollectionUtils.isEmpty(constructorArgs)) {
                bean = clazz.newInstance();
            }else {
                Object[] realArgs = new Object[constructorArgs.size()];
                int index = 0;
                for (Property property : constructorArgs) {
                    if (property.isValue()){
                        realArgs[index++] = property.getValue();
                    }else {
                        realArgs[index++] = getBean(property.getRef());
                    }
                }
                List<Class<?>> argsClass = constructorArgs.stream().map(Property::getClazz).collect(Collectors.toList());
                Constructor constructor = clazz.getConstructor(argsClass.toArray(new Class[]{}));
                bean = constructor.newInstance(realArgs);
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("没有默认构造方法，class = " + className);
        }
        return bean;
    }

    private void populateBean(Object bean, BeanDefinition beanDefinition) {
        for (Field field : beanDefinition.getClazz().getDeclaredFields()) {
            String fieldName = field.getName();
            Property property = beanDefinition.getProperty(fieldName);
            if (property != null) {
                if (property.isValue()) {
                    ReflectionUtils.injectField(field, bean, property.getValue());
                }

                if (property.isRef()){
                    ReflectionUtils.injectField(field, bean, getBean(fieldName));
                }
            }
        }
    }



    protected boolean isMyAutoWire(Field field) {
        Annotation annotation = field.getAnnotation(MyAutoWire.class);
        return annotation != null;
    }

    protected boolean isMyComponent(Class clazz) {
        Annotation annotation = clazz.getAnnotation(MyComponent.class);
        return annotation != null;
    }

    private void instantiationSingleton() {
        beanDefinitionMap.forEach((k, v) -> {
            if (v.getSingleton()) {
                getBean(k);
            }
        });
    }
}
